package pingu.monde;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;

import javax.swing.plaf.basic.BasicInternalFrameTitlePane.MaximizeAction;

import pingu.joueur.Joueur;

public class Plateau implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2661445249097246597L;
	private Case[][] plateau;
	private int nbCases;
	private int largeur,hauteur;


	public Plateau(int nbCases) {
		this.nbCases=nbCases;
		initialiserPlateau();
		this.largeur=(plateau.length)/2;
		this.hauteur=plateau[0].length;
	}

	public Plateau(int hauteur,int largeur) {
		this.nbCases=hauteur*largeur;
		initialiserPlateau(hauteur, largeur);
		this.largeur=largeur;
		this.hauteur=hauteur;
	}


	private void initialiserPlateau(int hauteur, int largeur) {
		plateau=new Case[largeur*2][hauteur];
		for (int i = 0; i < largeur; i++) {
			for (int j = 0; j < hauteur; j++) {
				setCase(i, j, new Case(this,i,j));
			}
		}
	}

	private void initialiserPlateau() {
		int largeur =(int) (Math.sqrt(nbCases)*2);  //*1.3);   1,3 represent the 800x600 ratio
		int hauteur =(int) (Math.sqrt(nbCases)*2*0.75); //*O.75);   O.75 represent the 800x600 ratio
		int nbCasesCourant=largeur*hauteur;
		initialiserPlateau(hauteur, largeur);

		while (nbCasesCourant>nbCases) {
			int xRand=(int) (Math.random()*largeur);
			int yRand=(int) (Math.random()*hauteur);
			Case caseCourante=getCase(xRand, yRand);
			if(caseCourante!=null){
				int minNbVoisinsPourVoisins=6;
				ArrayList<Case> voisins=getVoisins(xRand, yRand);
				for (Iterator iterator = voisins.iterator(); iterator.hasNext();) {
					Case voisinCourant = (Case) iterator.next();
					if(voisinCourant!=null){
						int xCourant=voisinCourant.getX();
						int yCourant=voisinCourant.getY();
						minNbVoisinsPourVoisins=Math.min(minNbVoisinsPourVoisins,getNbVoisins(xCourant, yCourant));
					}					
				}
				if(getNbVoisins(xRand,yRand)<=3&&minNbVoisinsPourVoisins>2){
					setCase(xRand, yRand,null);
					nbCasesCourant--;
				}
			}			
		}
		recentrerPlateau( largeur, hauteur);
	}

	private void recentrerPlateau(int largeur,int hauteur) {
		int hauteurMin=-1;
		for (int h = 0; h < hauteur; h++) {
			for (int l = 0; l < largeur; l++) {
				if(getCaseSurHorizontale(0, h, l)!=null) hauteurMin=h;
			}
			if(hauteurMin!=-1) break;
		}

		int hauteurMax=-1;
		for (int h = hauteur-1; h >= 0; h--) {
			for (int l = 0; l < largeur; l++) {
				if(getCaseSurHorizontale(0, h, l)!=null) hauteurMax=h;
			}
			if(hauteurMax!=-1) break;
		}

		int largeurMin=-1;
		for (int l = 0; l < largeur; l++) {
			for (int h = 0; h < hauteur; h++) {
				if(getCase(l, h)!=null) largeurMin=l;
			}
			if(largeurMin!=-1) break;
		}

		int largeurMax=-1;
		for (int l = largeur-1; l >= 0; l--) {
			for (int h = 0; h < hauteur; h++) {
				if(getCase(l, h)!=null) largeurMax=l+1;
			}
			if(largeurMax!=-1) break;
		}

		//pb dans les max min ===> largeur surestim�e (l+1) parfois � cause du d�calage 


		int larg=1+largeurMax-largeurMin;
		int haut=1+hauteurMax-hauteurMin;
		//System.out.println("largeur hauteur lmax lmin hmax hmin larg haut:  "+largeur+" "+hauteur+" - "+largeurMax+" "+largeurMin+" - "+hauteurMax+" "+hauteurMin+" - "+larg+" "+haut);
		Case[][] plateauTmp=new Case[larg*2][haut];
		for (int y = 0; y < haut; y++) {
			for (int x = 0; x < larg; x++) {
				Case caseCourante;
				if (hauteurMin%2==0){
					caseCourante=getCase(x+largeurMin, y+hauteurMin);
					setCase(plateauTmp, x, y, caseCourante);
				}
				else {
					caseCourante=getCase(x+largeurMin-((y+1)%2), y+hauteurMin);
					setCase(plateauTmp, x, y, caseCourante);
				}
				if(caseCourante!=null){
					caseCourante.setX(x);
					caseCourante.setY(y);
				}

				//+((hauteurMin+1)%2)
				//plateauTmp[i][j]=plateau[i+largeurMin][j+hauteurMin];
			}
		}
		plateau=plateauTmp;

		//System.out.println(toString(plateauTmp));
		//System.out.println("________________________");
	}

	public int getNbVoisins(int x, int y) {
		ArrayList<Case> voisins=getVoisins(x, y);
		int nbVoisins=0;
		for (int i = 0; i < voisins.size(); i++) {
			if (voisins.get(i)!=null) {
				nbVoisins++;
			}
		}
		return nbVoisins;
	}

	public ArrayList<Case> getVoisins(int x, int y) {
		ArrayList<Case> voisins=new ArrayList<Case>();

		voisins.add(getCaseSurHorizontale(x, y, -1));
		voisins.add(getCaseSurDiagonaleNO(x, y, -1));
		voisins.add(getCaseSurDiagonaleNE(x, y, -1));
		voisins.add(getCaseSurHorizontale(x, y, 1));
		voisins.add(getCaseSurDiagonaleNO(x, y, 1));
		voisins.add(getCaseSurDiagonaleNE(x, y, 1));

		return voisins;
	}
	private Case getCaseSurDiagonaleNO(int xCaseRef, int ycaseRef, int deplacement){
		int x=xCaseRef*2+ycaseRef%2;
		int y=ycaseRef;		
		try {
			return plateau[x+deplacement][y+deplacement];
		} catch (Exception e) {
			//System.out.println("Exception :"+ e);
			return null;
		}
	}

	private Case getCaseSurDiagonaleNE(int xCaseRef, int ycaseRef, int deplacement){
		int x=xCaseRef*2+ycaseRef%2;
		int y=ycaseRef;		
		try {
			return plateau[x-deplacement][y+deplacement];
		} catch (Exception e) {
			//System.out.println("Exception :"+ e);
			return null;
		}
	}

	private Case getCaseSurHorizontale(int xCaseRef, int ycaseRef, int deplacement){
		int x=xCaseRef*2+ycaseRef%2;
		int y=ycaseRef;		
		try {
			return plateau[x+deplacement*2][y];
		} catch (Exception e) {
			//System.out.println("Exception :"+ e);
			return null;
		}
	}

	public boolean casesAtteignablesEnPingouin(Case caseDepart, Case caseArrivee) {
		if(caseArrivee==null||caseDepart==null)return false;
		ArrayList<Case> listeCases=casesAtteignablesEnPingouin(caseDepart);
		boolean result=caseArrivee.estLibre()&&listeCases.contains(caseArrivee);
		
		if(!result){
			for (int i = 0; i < listeCases.size(); i++) {
				listeCases.get(i).setSelection(true);
			}
		}
		return result;
	}

	public ArrayList<Case> casesAtteignablesEnPingouin(Case caseDepart) {

		ArrayList<Case> listeCases=new ArrayList<Case>();

		Case caseNE,caseE,caseSE,caseSO,caseO,caseNO;
		caseNE=getCaseSurDiagonaleNE(caseDepart.getX(), caseDepart.getY(), -1);
		caseE=getCaseSurHorizontale(caseDepart.getX(), caseDepart.getY(), 1);
		caseSE=getCaseSurDiagonaleNO(caseDepart.getX(), caseDepart.getY(), -1);
		caseSO=getCaseSurDiagonaleNE(caseDepart.getX(), caseDepart.getY(), 1);
		caseO=getCaseSurHorizontale(caseDepart.getX(), caseDepart.getY(), -1);
		caseNO=getCaseSurDiagonaleNO(caseDepart.getX(), caseDepart.getY(), 1);

		boolean casesToutesTestees=false;


		while (!casesToutesTestees){
			casesToutesTestees=true;


			if(caseNE!=null&&caseNE.estLibre()){
				listeCases.add(caseNE);
				caseNE=getCaseSurDiagonaleNE(caseNE.getX(), caseNE.getY(), -1);
				casesToutesTestees=false;
			}
			if(caseE!=null&&caseE.estLibre()){
				listeCases.add(caseE);
				caseE=getCaseSurHorizontale(caseE.getX(), caseE.getY(), 1);
				casesToutesTestees=false;
			}
			if(caseSE!=null&&caseSE.estLibre()){
				listeCases.add(caseSE);
				caseSE=getCaseSurDiagonaleNO(caseSE.getX(), caseSE.getY(), -1);
				casesToutesTestees=false;
			}
			if(caseSO!=null&&caseSO.estLibre()){
				listeCases.add(caseSO);
				caseSO=getCaseSurDiagonaleNE(caseSO.getX(), caseSO.getY(), 1);
				casesToutesTestees=false;
			}
			if(caseO!=null&&caseO.estLibre()){
				listeCases.add(caseO);
				caseO=getCaseSurHorizontale(caseO.getX(), caseO.getY(), -1);
				casesToutesTestees=false;
			}
			if(caseNO!=null&&caseNO.estLibre()){
				listeCases.add(caseNO);
				caseNO=getCaseSurDiagonaleNO(caseNO.getX(), caseNO.getY(), 1);
				casesToutesTestees=false;
			}

		}
		return listeCases; 
	}

	public ArrayList<Case> getCasesPlateau(){
		ArrayList<Case> listeCases=new ArrayList<Case>();
		for (int i = 0; i < largeur; i++) {
			for (int j = 0; j < hauteur; j++) {
				Case curCase=getCase(i,j);
				if(curCase!=null) listeCases.add(curCase);
			}
		}
		return listeCases;
	}

	public void selectionneCase(Case laCase){

		for (int y = 0; y < hauteur; y++) {
			for (int x = 0; x < largeur; x++) {
				Case caseCourante=getCase(x, y);
				if(caseCourante!=null)caseCourante.setSelection(false);
			}
		}
		if(laCase!=null)laCase.setSelection(true);		
	}

	public Case getCaseSelectionnee(){

		for (int y = 0; y < hauteur; y++) {
			for (int x = 0; x < largeur; x++) {
				Case caseCourante=getCase(x, y);
				if(caseCourante!=null&&caseCourante.getSelection())return caseCourante;
			}
		}
		return null;
	}

	public void setCase(int x,int y,Case laCase){
		setCase(this.plateau, x, y, laCase);
	}

	public Case getCase(int x,int y){
		return getCase(this.plateau, x, y);
	}

	public void setCase(Case[][] plateau, int x,int y,Case laCase){
		try {
			plateau[x*2+y%2][y]=laCase;
		} catch (Exception e) {
			//System.out.println("Exception :"+ e);
		}
	}

	public Case getCase(Case[][] plateau, int x,int y){
		try {
			return plateau[x*2+y%2][y];
		} catch (Exception e) {
			//System.out.println("Exception :"+ e);
			return null;
		}
	}
	public Case[][] getPlateau() {
		return plateau;
	}

	public void setPlateau(Case[][] plateau) {
		this.plateau = plateau;
	}

	public int getLargeur() {
		return largeur;
	}

	public int getHauteur() {
		return hauteur;
	}

	public String toString(){
		int largeur=plateau.length;
		int hauteur=plateau[0].length;
		String plateauString="";

		for (int j = 0; j < hauteur; j++) {
			for (int i = 0; i < largeur; i++) {
				try {
					Case caseCourante=plateau[i][j];
					if (caseCourante!=null) {
						plateauString+=caseCourante.toString();
					}
					else {
						plateauString+="   ";
					}
				} catch (Exception e) {}
			}
			plateauString+="\n\n";
		}
		return plateauString;
	}
	public String toString(Case[][] plateau){
		int largeur=plateau.length;
		int hauteur=plateau[0].length;
		String plateauString="";

		for (int j = 0; j < hauteur; j++) {
			for (int i = 0; i < largeur; i++) {
				try {
					Case caseCourante=plateau[i][j];
					if (caseCourante!=null) {
						plateauString+=caseCourante.toString();
					}
					else {
						plateauString+="   ";
					}
				} catch (Exception e) {}
			}
			plateauString+="\n\n";
		}
		return plateauString;
	}

	public int getScore(Joueur jCourant, ArrayList<Joueur> joueurs, Case caseCur) {

		int size=casesAtteignablesEnPingouin(caseCur).size();
		int somme=0;

		if(caseCur.estLibre()){
			caseCur.setPasFondue(false);
			for (Joueur joueur : joueurs) {
				if(joueur!=jCourant){
					ArrayList<Pingouin> listePing = joueur.getPingouins();

					for (Pingouin pingouin : listePing) {
						somme+=casesAtteignablesEnPingouin(pingouin.getCase()).size();
					}
				}
			}
			caseCur.setPasFondue(true);
		}
		return size-somme;
	}

	public Case getCaseFreeCase(int joueurCourant, ArrayList<Joueur> joueurs){
		ArrayList<Pingouin> lisPing = joueurs.get(joueurCourant).getPingouins();
		
		float maxScorePingu=-10000;
		Pingouin maxPingu=null;
		Case maxCasePingu=null;
		Case maxCase=null;
		
		for (Pingouin pingouin : lisPing) {
			Case caseDepart=pingouin.getCase();
			ArrayList<Case> listeCases=casesAtteignablesEnPingouin(caseDepart);
			
			float maxScoreCase=-10000;
			
			for (Case caseCur : listeCases) {						
				int score=getScoreFreeCases(joueurCourant,joueurCourant,joueurs,caseCur,0,2);
				if(score>maxScoreCase){
					maxScoreCase=score;
					maxCase=caseCur;
				}
			}
			if(maxScoreCase>maxScorePingu){
				maxScorePingu=maxScoreCase;
				maxPingu=pingouin;
				maxCasePingu=maxCase;
				System.out.println("maxPingu X: "+maxPingu.getCase().getX()+" maxPingu Y: "+maxPingu.getCase().getY()+" maxScore: "+maxScorePingu);
			}
		}
		return maxCasePingu;
		
	}
	
	public int getScoreFreeCases(int jToScore,int jCourant, ArrayList<Joueur> joueurs, Case caseCur,int profondeur,int profMax) {

		if(jToScore==jCourant&&profondeur>profMax) return profondeur; 	
		int bestScore=-1;

		if(caseCur!=null)caseCur.setPasFondue(false);


		int nextPlayer=(jCourant+1)%joueurs.size();
		if(jToScore==nextPlayer) profondeur+=1;
		ArrayList<Pingouin> listePing = joueurs.get(nextPlayer).getPingouins();

		int casesAtteignables=0;
		for (Pingouin pingouin : listePing)
		{
			ArrayList<Case> listCases=casesAtteignablesEnPingouin(pingouin.getCase());
			for (Case nextCase : listCases)
			{
				int nextScore=getScoreFreeCases(jToScore,nextPlayer,joueurs,nextCase,profondeur,profMax);
				bestScore=Math.max(bestScore, nextScore);
			}
			casesAtteignables+=listCases.size();			
		}
		if(casesAtteignables==0){
			if(jToScore==nextPlayer) bestScore=profondeur;
			else{
				int nextScore=getScoreFreeCases(jToScore,nextPlayer,joueurs,null,profondeur,profMax);
				bestScore=Math.max(bestScore, nextScore);
			}
		}

		if(caseCur!=null) caseCur.setPasFondue(true);


		return bestScore;
	}


	public Object[] getNextBestCase(int jCourant, ArrayList<Joueur> joueurs){

		boolean allPingouinsInZonesClosed=true;
		ArrayList<Pingouin> listePing = joueurs.get(jCourant).getPingouins();
		for (Pingouin pingouin : listePing) {
			allPingouinsInZonesClosed=allPingouinsInZonesClosed&&isAloneInclosedZone(pingouin);
		}
		Object[] casePlusPing=new Object[2];
		if(allPingouinsInZonesClosed){
			System.out.println("Gestion de zone fermée");
			casePlusPing=getNextCaseClosedZone(jCourant,joueurs);
		}
		else{
			System.out.println("Gestion du prochain coup");
			casePlusPing=getNextCase(jCourant,joueurs);
		}
		return casePlusPing;

	}

	private Object[] getNextCase(int jCourant, ArrayList<Joueur> joueurs) {


		ArrayList<Pingouin> listePing = joueurs.get(jCourant).getPingouins();
		Case bestZoneCase=null;
		Pingouin bestPingouin=null;
		
		for (Pingouin pingouin : listePing) { //TODO parallelisable 
			ArrayList<Case> listCases=casesAtteignablesEnPingouin(pingouin.getCase());
			int bestScore=-10000;
			for (Case nextCase : listCases)
			{
				ArrayList<Case> listeCasesFondues = new ArrayList<Case>();
				nextCase.setPasFondue(false);
				listeCasesFondues.add(nextCase);
				
				
				int scoreJcourant=0;
				int bestOtherJScore=0;
				//Case bestZoneCaseJcourant = null;
				
				for (int jIterator = 1; jIterator < joueurs.size()+1; jIterator++) {
					int indexJoueur=(jCourant+jIterator)%joueurs.size();

					ArrayList<Pingouin> listePingCour = joueurs.get(indexJoueur).getPingouins();
					
					Case bestZoneCaseCour=null;
					int bestScoreCour=0;
					
					for (Pingouin pingouinCour : listePingCour) {
						ArrayList<Case> listCasesCour=casesAtteignablesEnPingouin(pingouinCour.getCase());
						
						for (Case nextCaseCour : listCasesCour)
						{
							int score=getScoreZoneCase(indexJoueur, joueurs);
							if(score>bestScoreCour){
								bestScoreCour=score;
								bestZoneCaseCour=nextCaseCour;
								//System.out.println("Joueur n" +indexJoueur+ " - Score:"+bestScoreCour);
							}
						}
					}
					//seule la dernière actualisation de scoreJcourant sera utilisé pour scoreJcourant
					if(bestZoneCaseCour!=null){
						bestZoneCaseCour.setPasFondue(false);
						listeCasesFondues.add(bestZoneCaseCour);
						if(indexJoueur==jCourant)scoreJcourant=bestScoreCour;
						else bestOtherJScore=Math.max(bestOtherJScore, bestScoreCour);
						//bestZoneCaseJcourant=bestZoneCaseCour;
					}
				}
				// Score=score maximum pour le 2eme coup du JCourant -score Maximum des autres joueurs
				int customScore= scoreJcourant-bestOtherJScore;
				if(customScore>bestScore){
					bestScore=customScore;
					bestZoneCase=nextCase;
					bestPingouin=pingouin;
					System.out.println("Joueur n" +jCourant+ " - Score:"+scoreJcourant+" - Score adversaire:"+bestOtherJScore);
				}
				for (Case caseFondue : listeCasesFondues) {
					caseFondue.setPasFondue(true);
				}
				
			}//fin for toutes cases du pingouin
		}//fin pour tous pingouins
		Object[] result={bestPingouin,bestZoneCase};
		return result;

	}

	//attention calculé de tous les cotés même si blocage
	private int getScoreZoneCase(int jCourant, ArrayList<Joueur> joueurs) {
		
		ArrayList<ArrayList<Case>> startingZones=new ArrayList<ArrayList<Case>>();
		ArrayList<Case> currentZones=new ArrayList<Case>();
		for (Joueur joueur : joueurs) {
			ArrayList<Case> caseJoueur=new ArrayList<Case>();
			ArrayList<Pingouin> listePingCour = joueur.getPingouins();
			for (Pingouin pingouin : listePingCour) {
				caseJoueur.add(pingouin.getCase());
				currentZones.add(pingouin.getCase());
			}
			startingZones.add(caseJoueur);
		}
		
		ArrayList<Case> jCourantZones=new ArrayList<Case>();
		boolean zoneGrowing=true;
		int score=0;
		while(zoneGrowing){
			zoneGrowing=false;
			for (int jIterator = 0; jIterator < joueurs.size(); jIterator++) {
				int indexJoueur=(jCourant+jIterator)%joueurs.size();

				if(startingZones.get(indexJoueur).size()>0){
					Case caseCur=startingZones.get(indexJoueur).remove(0);
					ArrayList<Case> casesAutour=new ArrayList<Case>();
					casesAutour.add(getCaseSurDiagonaleNE(caseCur.getX(), caseCur.getY(), -1));
					casesAutour.add(getCaseSurHorizontale(caseCur.getX(), caseCur.getY(), 1));
					casesAutour.add(getCaseSurDiagonaleNO(caseCur.getX(), caseCur.getY(), -1));
					casesAutour.add(getCaseSurDiagonaleNE(caseCur.getX(), caseCur.getY(), 1));
					casesAutour.add(getCaseSurHorizontale(caseCur.getX(), caseCur.getY(), -1));
					casesAutour.add(getCaseSurDiagonaleNO(caseCur.getX(), caseCur.getY(), 1));
					for (Case caseAutour : casesAutour) {
						if(caseAutour!=null&&caseAutour.estLibre()&&!currentZones.contains(caseAutour)) {
							currentZones.add(caseAutour);
							startingZones.get(indexJoueur).add(caseAutour);
							if(indexJoueur==jCourant){
								jCourantZones.add(caseAutour);
								score+=caseAutour.getNbPoissons();
							}
							zoneGrowing=true;
						}
					}
				}
			}
		}
		selectionneCase(null);
		return score;
	}

	private Object[] getNextCaseClosedZone(int jCourant, ArrayList<Joueur> joueurs) {
		ArrayList<Pingouin> listePing = joueurs.get(jCourant).getPingouins();
		for (Pingouin pingouin : listePing) {
			// TODO Auto-generated method stub
		}
		return null;

	}

	private boolean isAloneInclosedZone(Pingouin pingouin) {

		ArrayList<Case> maCase=new ArrayList<Case>();
		maCase.add(pingouin.getCase());
		ArrayList<Case> listCases=getCasesAutour(maCase);
		for (Case caseCur : listCases) {
			Pingouin pingouinCur=caseCur.getMonPingouin();
			if(pingouinCur!=null&&(pingouinCur.getJoueur()!=pingouin.getJoueur())) return false;
		}

		return true;
	}

	private ArrayList<Case> getCasesAutour(ArrayList<Case> startingCases) {
		ArrayList<Case> mesCases = new ArrayList<Case>(startingCases);
		while(!startingCases.isEmpty()) {
			Case caseCur=startingCases.remove(0);
			Case caseNE,caseE,caseSE,caseSO,caseO,caseNO;
			caseNE=getCaseSurDiagonaleNE(caseCur.getX(), caseCur.getY(), -1);
			caseE=getCaseSurHorizontale(caseCur.getX(), caseCur.getY(), 1);
			caseSE=getCaseSurDiagonaleNO(caseCur.getX(), caseCur.getY(), -1);
			caseSO=getCaseSurDiagonaleNE(caseCur.getX(), caseCur.getY(), 1);
			caseO=getCaseSurHorizontale(caseCur.getX(), caseCur.getY(), -1);
			caseNO=getCaseSurDiagonaleNO(caseCur.getX(), caseCur.getY(), 1);
			if(caseNE!=null&&!mesCases.contains(caseNE)) {mesCases.add(caseNE);startingCases.add(caseNE);}
			if(caseE!=null&&!mesCases.contains(caseE)) {mesCases.add(caseE);startingCases.add(caseE);}
			if(caseSE!=null&&!mesCases.contains(caseSE)) {mesCases.add(caseSE);startingCases.add(caseSE);}
			if(caseSO!=null&&!mesCases.contains(caseSO)) {mesCases.add(caseSO);startingCases.add(caseSO);}
			if(caseO!=null&&!mesCases.contains(caseO)) {mesCases.add(caseO);startingCases.add(caseO);}
			if(caseNO!=null&&!mesCases.contains(caseNO)) {mesCases.add(caseNO);startingCases.add(caseNO);}
		}
		return mesCases;
	}

}
